詳解Terraform第3版 8章
https://gyazo.com/f7bec0609a1a743fbb4ca5e3e0e50472
本番レベルのTerraformコード
序
この章における「本番レベルのインフラ」: 会社の運命を任せられる種類のインフラ
ゼロからインフラ構築するのにどのくらいの期間が必要か、書籍表組参照
長く感じるようだが全く新しいインフラを作る人ならむしろ楽観的だろう
この章では以下を扱う
どうしてそのような時間を要するのか
本番レベルの意味(チェックリスト)
再利用可能な本番レベルのモジュールパターン
8.1 本番レベルのインフラ構築には時間がかかる理由
ソフトウェア開発における見積もりは不正確だし DevOps ならなおさら、とのこと
…いつでも予想以上の時間がかかるものである
筆者がなぜそう思うか
1. DevOps は産業分野として若造で十分に成熟していないから
クラウド, IaaS, DevOps が2000年代で Terraform, Docker, k8s は 2010年代半ば
2. Yak Shaving に影響されやすいから
本来のタスクに取り掛かるまでにさまざまな小さなタスクがある
技術とシステムデザインの未熟さから来ている結果とも言える
3. 本質的複雑性(essential complexity)を孕んでいるから
取り組む内容に関係なく存在する問題。実際にタスクリストが長くなる
tkdn.icon 特に依存関係の整理や順番が必ずあると思っててそこも本質的複雑性だと思う
一方で1, 2は選択したツールやプロセスによって強いられる問題が偶有的複雑性(accidental complexity)
8.2 本番レベルのインフラのチェックリスト
人によって観点が違う
メトリクスやアラートの必要性、キャパシティプランニングや可用性、自動テスト、セキュリティハードニング、データバックアップやログ集約
なのでほとんどの会社では必要条件や明確な定義はない
そういった意味ではどのインフラも違った形でデプロイされなにか重大な昨日が漏れている可能性がある
チェックリストを共有
表は書籍参照
インストール・設定・プロビジョニング・デプロイなどいくつかのタスクは気付ける
サービス回復力はどうか: サーバが落ちたら、LBが落ちたら、全停止したら
ネットワークタスクがやりにくい: VPC, VPN, サービスディスカバリ, SSHはセットアップに時間を要する
セキュリティが忘れられがち: 暗号化, シークレットの保存
8.3 本番レベルのインフラモジュール
tkdn.icon タスクリストからなぜか急に再利用可能なモジュールの話に……
8.3.1 小さなモジュール
すべての環境(Prod/Stg...)とすべてのインフラを1つのファイル,モジュールに定義しがち
数百行以上のコードを含む、あるいは密接に関係するインフラのパーツを数個以上デプロイする大きなモジュールは有害である
大きなモジュール dis シリーズ
遅い
クソデカリスク...少しの変更が大きなリスクにつながる
理解しにくい...
レビューしにくい...plan出力も数千行となり誰も読もうとしない
テストしにくい...9章で扱う
1つのことだけを行う小さなモジュールを組み合わせでコードにしましょう
tkdn.icon Unix哲学ほかいろいろなところで言われますね、なぜかボブおじの Clean Code から引用があった 比較的複雑なアーキテクチャ図(書籍参照)
20000行からなる1つファイルだとしたら…?
モジュール化の分割を図に示している
5章で取り組んでいた webserver-cluster モジュールを取り上げ
ASG + ALB + App、これらを3つの小さなモジュールにリファクタリングする
先にASGとALBを小さな2つのモジュールに分解
ASG
ALB
8.3.2 組み合わせ可能なモジュール
前述の小さなモジュールをどう組み合わせるか、改めてUnix哲学1つのことをうまくやるプログラム
tkdn.icon 関数の合成とあるが、Ruby の例や訳として正しいのかわからない。
compose = (funcA, funcB) => x => funcA(funcB(x)) みたいなの想像してた
副作用を最小化する
可能なかぎり外界から状態を読み込まずに入力パラメータを渡すと、外界に対して状態を書き出すことをせず、処理結果を出力パラメータとして返すこと
インフラコードにおいて副作用は避けられないが Terraform モジュールも考え方は一緒である
実践
ASGに関連する、モジュールをまたいで入力変数となるパラメータを variable に変更
モジュール利用者が出力を使ってSGにカスタムルール追加ができるよう振る舞いを変更
アプリケーション用のモジュールでこれまでのモジュールを利用し書き換える
8.3.3 テスト可能なモジュール
3つのモジュールを書いたが実際に動くか確認
これによって得られたもの
手動テストのハーネス(=テスト実行に必要なテスト実行用環境)
自動テストのハーネス: 自動テスト作成手段にもなる
実行可能なドキュメント: READMEを含んだサンプルコードがチームに展開される modules フォルダにある各 Terraform モジュールに対して examples フォルダにそれぞれサンプルコードを入れておくべき
tkdn.icon 正気か? けっこう大変そう
先にサンプルコードを書いておくことが重要
テストについての詳細は9章で扱うが、ここでは自己正常性確認が可能なモジュール(self-validating modules) 、つまり自身の振る舞いを確認できるモジュールに焦点をあてる
8.3.3.1 バリデーション
0.13〜 型制約以上のチェックのためバリデーションブロックが導入された
単一入力しかチェックできないため変数の組み合わせによる制約などがつけられない
8.3.3.2 事前条件と事後条件
1.2〜 precondition, postcondition ブロックが導入された
前段でハードコードしたinstance_typeが最新とは限らないので最新のinstance_typeを取得したいという例
postconditionブロックは apply 実行後のエラー補足
デプロイ後に ASG がマルチAZにデプロイされたのをチェックする例
self.<ATTRIBUTE> は postconditionのほか connection, provisioner ブロックのみで利用可能
8.3.3.3 バリデーションや事前条件・事後条件の使い時
validation -> 入力のサニタイズ
すべての本番レベルのモジュールで validation チェックを使いましょう
preconditionのほうが強力だが変数チャックは可能なかぎりvalidationで
precondition -> 基本的な前提チェック
デプロイされる前の前提をチェックするためにすべての本番レベルのモジュールで使いましょう
postcondition -> 約束事の強制
デプロイされた後のモジュールの振る舞いをチェックするためすべての本番レベルのモジュールで使いましょう
高度な前提条件・約束事の強制には自動テストツールを
Terraformが用意したものだけでは不十分である、9章でOPAやTerratestを扱う
8.3.4 バージョン管理されたモジュール
モジュールの依存関係のバージョン管理、モジュール自体のバージョン管理がある
前者でいうと…
Terraform コア: terraform バイナリのバージョン
プロバイダ: AWSプロバイダなど各プロバイダのバージョン
モジュール: モジュールブロクでダウンロードする依存するモジュールのバージョン
一般的なルールとしてバージョン固定するべき
デプロイが予測可能になる、繰り返し可能になる
Terraformコア
code:main.tf
terraform {
# 最低限メジャーバージョンを固定しよう
required_version = ">= 1.0.0, < 2.0.0"
# 本番レベルではすべて固定性
required_version = "1.7.4"
}
v1以前はtfstateの後方互換性が破壊的だったがv1以降は発生しない
うっかりバージョンアップするな
意図的に、注意深く、環境間で一貫性をもってTerraformのバージョンアップをせよ
tkdn.icon 通常のソフトウェア開発だと、ライブラリバージョンは pin して renovate などに任せることも多いが、Terraformコアのバージョンアップなどについては検討がつかない
tfenv を使って切り替えることも可能
.terraform-version による staging/production におけるアップグレードの切り分け
プロバイダ
code:main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
# 最低メジャーバージョン固定
version = "~> 5.0"
}
}
}
0.14〜 はロックファイルによって固定されるようになったので明示的に行う必要はない
ロックファイルによるバージョン固定 + プロバイダのコードが差し替えられたときのための安全確認のためチェックサム
モジュールのバージョン
4章でリポジトリのGitタグでバージョン管理しsourceに含めた
本章で扱った hello-world-app モジュール利用時にも同じようなコードを書くおさらい
またパブリックな Terraform レジストリに登録することも可能
制約
モジュールは GitHub リポジトリとして公開されている
terraform-<PROVIDER>-<NAME> というリポジトリの名前
規定のファイル構造
各リリースにセマンティックバージョニングに従いGitタグが利用されている
code:sample.tf
module "rds" {
source = "terraform-aws-modules/rds/aws"
version = "4.4.0"
# ...
}
8.3.5 Terraform モジュールのその先へ
本番レベルのインフラ全体を構築するためには Terraform のみならず
Docker, Packer, Chef, Puppet, Bash (DevOps界のダクトテープ) も使う必要はある
Terraformだけでは対応できない場合の対応をいくつか紹介
8.3.5.1 プロビジョナ
ブート処理や設定管理、後始末のためにTerraform実行のマシンでスクリプトを実行するためにある
remote-exec: EC2インスタンスとSGを使ってSSH接続するサンプル 参考 インスタンス起動までトライし続ける
https://gyazo.com/d30172b108bd9727785bcb6d17dcb501
デフォルトではリソース作成時のプロビジョナ、when = destroy を指定することで削除時に実行するプロビジョナになる
tkdn.icon オペレーションで入る以外あまりEC2でセットアップするようなことないからな…
コラム:
Terraformでサーバ上でスクリプト実行するのは面倒かつセキュアではない
ユーザデータスクリプトはEC2コンソールで管理できる
https://gyazo.com/428b055c668df0bcc81149b779ca1e82
プロビジョナでは使えないかつ16KBという制限がある
8.3.5.2 null_resource とプロビジョナ
特定のリソースに紐づけずプロビジョナを実行可能
null_resoure を再作成したい(=プロビジョナを実行したい)という目的のために、trigger が用意されている
code:main.tf
resource "null_resource" "example" {
provisioner "local-exec" {
command = "...."
}
triggers = {
# この場合はuuidビルトイン関数で毎回ユニークなidとなるのでapplyのたびに実行される
uuid = uuid()
}
}
8.3.5.3 外部データソース
プロビジョナが最適というシーンだけではない
external データソース
何らかのデータを受け取ってTerraformで値を受け取るためにスクリプトを実行する際に使う
プロトコル
Terraform -> 外部プログラムにデータを渡す際は query 引数を使いJSONとして標準入力から読み込む
外部プログラムは標準出力 -> Terraform へ値を返し data.external.<NAME>.result で参照可能
外部データが参照できて嬉しいものの移植性は下がるので控えめに
8.4 まとめ
1. 本番レベルの表を確認してチェックリストを使ってください
2. サンプルコードを先に書いてモジュールの使用方法やドキュメント、合理的なデフォルト値を用意してください
3. モジュールは小さく再利用可能で組み合わせ可能なモジュールの集合を作ってください
Docker, Packer, Bash などのツールも適宜組み合わせましょう
Terraformコア、プロバイダ、モジュールのバージョンは固定してください
4. テストフォルダを作ってサンプルコードに自動テストを書いてください